
<template>
  <Modal v-model="drawer.show" transfer :title="lang.title" :width="1024"
    :styles="{top: '20px', maxHeight: 'calc(100vh - 30px)', overflow: 'auto'}"
    :closable="true" :mask-closable="false" @on-ok="submit">
    <div v-if="drawer.show">
      <Row :gutter="8" class="c-formulaEditor">
        <Col :span="menuRate">
        <Menu style="width:100%;height:100%;" 
          :active-name="currentMenu" @on-select="menuChange" :open-names="['params']" >
          <!-- <MenuItem :name="'start'" @click.native="selectMenu('start')">{{ lang.start }}</MenuItem> -->
          <MenuItem :name="'paramList'">{{ lang.paramList }}</MenuItem>
          <Submenu :name="'params'" style="max-height: calc(100vh - 315px);overflow: auto;margin-right: 6px;">
            <template slot="title">
              <Icon type="ios-list" />
              {{ lang.params }}
            </template>
            <MenuItem v-for="(item, i) in data.params" :key="i" :name="'step' + i">
              {{`${lang.step}:${i}`}}
              <Icon style="color:red;padding: 0 8px;line-height: 46px;" type="md-remove"
                @click.native="removeStep(i)">
              </Icon>
            </MenuItem>
            <MenuItem name="new">{{ lang.newStep }}</MenuItem>
          </Submenu>
          <MenuItem :name="'expression'">{{ lang.expression }}</MenuItem>
          <MenuItem :name="'back'" >{{ lang.back }}</MenuItem>
        </Menu>
        </Col>
        <Col :span="24 - menuRate" style="padding:10px;">
        <div v-if="currentMenu === 'paramList'">
            <cfg-field :mark="false" v-for="item in Object.keys(data.param)" :key="item"
              :config="{ type: 'text' }" :label="item" v-model="data.param[item]" :removeField="true"
              @removeField="removeParam(item)"></cfg-field>
            <cfg-empty :mark="false" :label="lang.newParam" :config="{ type: 'text' }" :addField="true"
              @addField="addParam"></cfg-empty>
        </div>
        <div v-if="currentMenu === 'back'">
          <cfg-field :mark="false" :label="lang.backData" :config="{ type: 'text', }" v-model="data.saveToField"/>
        </div>
        <div v-else-if="stepIndex !== -1">
          <cfg-field :mark="false" :label="lang.serviceModel" :config="{ type: 'select', options: modelList }"
            v-model="data.params[stepIndex].model" @change="modelChange"/>
          <cfg-field :mark="false" :label="lang.serviceAction" :config="{ type: 'select', options: actionList }"
            v-model="data.params[stepIndex].action" />

          <template v-if="!!curParams.action && !!curParams.model">
            <component :is="`cfg-${curParams.model}-model-pointer`" :label="lang.refModel"
              v-model="data.params[stepIndex].pointer"  @change="pointerChange"/>
          </template>

          <cfg-field :mark="false" :label="lang.condition" :config="{ type: 'text' }"
            v-model="data.params[stepIndex].condition" />
          <cfg-field :mark="false" :label="lang.backType" :config="{ type: 'select', options: backOptions }"
            v-model="data.params[stepIndex].result.type"/>
          <cfg-field :mark="false" :label="lang.backPath"
            :config="{ type: 'multiSelect', options: fieldList[stepIndex] }"
            v-model="data.params[stepIndex].result.path"/>
          <cfg-field :mark="false" :label="lang.backData" :config="{ type: 'text', }"
            v-model="data.params[stepIndex].result.var"/>
        </div>
        <div :class="{hidden: currentMenu !== 'expression', 'expWp': true}">
          <textarea class="ivu-input expInput" @change="expInputChange" @blur="expInputBlur" 
            wrap="soft" autocomplete="off" spellcheck="false" rows="6"
            v-model="data.expression" ref="expInput"></textarea>
          <!-- <codemirror ref="myCm"
              v-model="data.expression" 
              :options="cmOptions"
              @ready="onCmReady"
              @focus="onCmFocus"
              @input="expInputChange">
          </codemirror> -->
          <cfg-field :mark="false" :label="lang.expFunction"
            :config="{ type: 'select', options: functionList }"
            v-model="formData.function" @select="val => expChange('func', val.value)"/>
          <cfg-field :mark="false" :label="lang.expParam"
            :config="{ type: 'select', options: expParamList }"
            v-model="formData.param" @select="val => expChange('variable', val.value)"/>
          <div class="btnwp">
            <Button v-for="item in expOperatorList" :key="item" @click="expChange('calc', item)">{{ item }}</Button>
            <span class="float-right">
              <Button class="btnValidate" @click="validate" type="primary" ghost>{{ lang.verify }}</Button>
            </span>
          </div>
          <p class="ivu-mt" >{{ lang.verifyResult }}：
            <span v-if="validateMsg.content" 
              :style="{color: validateMsg.status === 'error' ? '#ed4014' : '#2d8cf0'}"
              >{{ validateMsg.content }}</span>
          </p>
        </div>
        </Col>
      </Row>
    </div>
  </Modal>
</template>

<script>
// import { codemirror } from 'vue-codemirror'
// import 'codemirror/lib/codemirror.css'
// import 'codemirror/mode/javascript/javascript'
import {mapActions} from "vuex";
import CfgWizardModal from "../../wizard/parts/cfg-wizard-modal";
import CfgField from "../../wizard/fields/cfg-field";
import CfgMapWrapper from '../../wizard/map/cfg-map-wrapper';
import CfgEmpty from "../../wizard/fields/cfg-empty";
// import CfgTwinsInstancePointer from "../../wizard/fields/cfg-twins-instance-pointer";
// import CfgLogicInstancePointer from "../../wizard/fields/cfg-logic-instance-pointer";
import CfgEntityModelPointer from "../../wizard/fields/cfg-entity-model-pointer";
// import CfgLogicModelPointer from "../../wizard/fields/cfg-logic-model-pointer";
import CfgTemplateModelPointer from "../../wizard/fields/cfg-template-model-pointer";
import CfgStatisticsModelPointer from "../../wizard/fields/cfg-statistics-model-pointer";

export default {
  name: "formulaEditor",
  components: { CfgField, CfgWizardModal, CfgMapWrapper, CfgEmpty, CfgTemplateModelPointer, CfgStatisticsModelPointer },
  data() {
    return {
      model: null,
      modelList: [],
      currentMenu: '',
      formData: {
        function: '', param: ''
      },
      data: {
        saveToField: "",
        expression: "",
        param: {},
        params: [{}]
      },
      stepIndex: -1,
      entityModalDrawer: { show: false },
      backOptions: [ { label: 'object', value: 'object' }, { label: 'list', value: 'list' }, ],
      actionList: [ {label: '条件查询', value: 'query'} ],
      fieldList: [],
      functionList: [
        { label: 'sum ( param )', value: 'sum' },
        { label: 'average ( param )', value: 'average' },
        { label: 'max ( param )', value: 'max' },
        { label: 'min ( param )', value: 'min' },
      ],
      expParamList: [],
      expOperatorList: ['+', '-', '*', '/', '(', ')', ],
      validateMsg: {
        status: '',
        content: ''
      },
      // editor: {},
      varObj: {}, // 变量Object，用来校验表达式
      inputPosition: 0,
      cmOptions: {
        // codemirror options
        tabSize: 4,
        mode: 'text/javascript',
        theme: 'base16-dark',
        lineWrapping: true,
        showCursorWhenSelecting: true,
        // lineNumbers: true,
        line: true,
        matchBrackets: true,
        autoCloseTags: true,
        // more codemirror options, 更多 codemirror 的高级配置...
      }
    }
  },
  props: {
    drawer: {
      default: () => {
        return {
          show: false
        };
      }
    },
    width: {
      default: () => {
        return 800;
      }
    },
    menuRate: {
      default: () => {
        return 6;
      }
    },
    value: {
      default: () => {
        return {
          saveToField: "",
          expression: "",
          param: {},
          params: []
        };
      }
    },
    position: {
      default: () => {
        return '';
      }
    },
  },
  computed: {
    lang() {
      return {
        "en-US": {
          title: 'formula edit',
          start: 'formula start',
          params: 'formula params',
          eventBack: 'formula back',
          back: 'formula return',
          step: 'step',
          newStep: 'new step',
          expFunction: 'select formula',
          expParam: 'select param',
          // state: 'state list',
          // newState: 'new state',
          // param: 'parameter',
          // newParam: 'new parameter',
          backType: 'save type',
          backPath: 'save path',
          backData: 'save to param',
          saveToField: 'return data',
          serviceModel: 'model',
          serviceAction: 'action',
          refModel: 'reference model',
          name: 'name',
          id: 'id',
          condition: 'condition',
          saveTo: 'save to state',
          subscribeModel: 'runSubscribe model',
          pointer: 'pointer',
          registerModel: 'runRegister model',
          expression: 'expression',
          verify: 'verify',
          verifyResult: 'verify result',
          verifySuccess: 'verify success',
          paramList: 'param list',
        },
        "zh-CN": {
          title: '公式编辑',
          start: '公式开始',
          params: '公式参数',
          eventBack: '返回',
          back: '公式返回',
          step: '步骤',
          newStep: '新增步骤',
          expFunction: '选择公式',
          expParam: '选择参数',
          // state: '变量表',
          // newState: '新变量',
          // param: '参数表',
          // newParam: '新参数',
          backType: '保存类型',
          backPath: '保存路径',
          backData: '保存到变量',
          saveToField: '返回数据',
          serviceModel: '模型',
          serviceAction: '活动',
          refModel: '参考模型',
          name: '名称',
          id: '唯一标识',
          condition: '条件',
          saveTo: '保存到变量',
          subscribeModel: '订阅模型',
          pointer: '指针',
          registerModel: '注册模型',
          expression: '表达式',
          verify: '验证',
          verifyResult: '验证结果',
          verifySuccess: '校验通过',
          paramList: '参数表'
        },
      }[this.G_Locale];
    },
    curParams() {
      // console.log('curParams', this.data.params[this.stepIndex]);
      return this.data.params[this.stepIndex]
    },
    paramList() {
      let opt = [];

      Object.keys(this.data.params).map(item => {
        item.result && opt.push(`<#${item.result.var}#>`)
      })
      return this.tools.getOptions(opt)
    },
    editor() {
      // return this.$refs.myCm.codemirror
    }
  },
  watch: {
    value: {
      handler(val) {
        if (val.params && val.params.length) {
          this.data = val;
          this.selectStep(0)
          this.inputPosition = val.expression.length
          this.calcFieldList()
          // this.data.param = this.data.param || {}
          console.log("🚀 ~ file: index.vue:354 ~ handler ~ this.data:", this.data)
        }
      },
      immediate: true
    },
    data() {
      this.calcFieldList()
    }
  },
  beforeMount() {
    this.selectStep(0)
    this.model = this.$modelObject.getClass('formula');
    this.modelList = this.model.serviceModelTpl();
  },
  mounted() {
  },
  methods: {
    ...mapActions('admin/model', [
      'getModel', 'getModelAll'
    ]),
    calcFieldList() {
      // 初始化model对应的字段下拉框列表
      this.getModelAll().then(allModel => {
        let tmpFieldList = []
        this.data.params.forEach((item, index) => {
          let pointer = item.pointer
          if (pointer && pointer.className && pointer.type) {
            let curModel = allModel.filter(model => 
              model.type === pointer.type && model.className === pointer.className
            );
            if (curModel.length && curModel[0].features && curModel[0].features.length){
              tmpFieldList[index] = this.tools.getOptions(curModel[0].features, 'fieldName', 'fieldName')
            }
          }
        })
        this.fieldList = tmpFieldList
      })
    },
    validate() {
      let content = this.data.expression.replace(/(<#)+/g, '$.').replace(/(#>)+/g, '')
      let res = this.tools.calcExp(this.varObj, content)

      if (res === true) {
        this.validateMsg.status = 'success'
        this.validateMsg.content = this.lang.verifySuccess
        this.$api.StatisticsInstance.statisticsCheck({
          statisticsInstanceHier: this.data
        }).then(res => {
          if (res && res.values) {
            console.log("🚀 ~ file: index.vue:348 ~ this.$api.StatisticsInstance.statistics ~ res:", res, this.data.saveToField)
            this.validateMsg.content = `${this.lang.verifySuccess}; (${res.values[this.data.saveToField]})`
          }
        })
      } else {
        this.validateMsg.status = 'error'
        this.validateMsg.content = res
      }
    },
    expInputBlur() {
      this.inputPosition =  this.$refs.expInput.selectionStart
      console.log("🚀 ~ file: index.vue:377 ~ expInputBlur ~ this.inputPosition:", this.inputPosition)
    },
    expInputChange(newCode) {
      console.log("🚀 ~ file: index.vue:380 ~ expInputChange ~ newCode:", newCode)
      this.validateMsg.content = ''
    },
    insertText(type, val) {
      let expression = this.data.expression;
      let pos = this.inputPosition
      val = type === 'func' ? `${val}()` : val;
      console.log("🚀 ~ file: index.vue:386 ~ insertText ~ pos:", pos)

      this.data.expression = expression.slice(0,pos) + val + expression.slice(pos)
      this.inputPosition = this.inputPosition + (type === 'func' ? val.length -1 : val.length)
    },
    expChange(type, val) {
      if (val) {
        this.insertText(type, val)
        return
        // this.validateMsg.content = ''
        // val += ' '
        if (type === 'variable') {
          this.insertContent({key: val, name: val}, type)
        } else {
          this.insertContent(val, type)
        }
      }
    },
    // value: { key: '', name: '' }, type: : 'variable' | 'func'
    insertContent(value, type) {
      const from = this.editor.getCursor();
      console.log("🚀 ~ file: index.vue:391 ~ insertContent ~ this.editor.getCursor():", this.editor.getCursor())
      if (type === 'variable') {
        this.editor.replaceSelection(value.key);
        const to = this.editor.getCursor();
        this.markText(from, to, value.name, 'cm-field');
      } else if (type === 'func') {
        this.editor.replaceSelection(`${value}()`);
        const to = this.editor.getCursor();
        this.markText( from, { line: to.line, ch: to.ch - 2 }, value, 'cm-func' );
        this.editor.setCursor({ line: to.line, ch: to.ch - 1 });
      } else if (typeof value === 'string') {
        this.editor.replaceSelection(value);
      }
      this.editor.focus();
    },
    markText( from, to, label, className = 'cm-func' ) {
      console.log("🚀 ~ file: markText ~ this.editor.lineCount()", this.editor.lineCount(),)

      const text = document.createElement('span');
      text.className = className;
      text.innerText = label;
      this.editor.markText(from, to, { atomic: true, replacedWith: text });
      console.log("🚀 ~ file: index.vue:402 ~ markText", from, to, label, className, this.data.expression)
    },
    onCmReady(cm) {
      console.log('the editor is readied!', this.editor,this.editor.lineCount(), this.data.expression.length)
      this.editor.setCursor({line: this.data.expression.length, ch: 0});
      this.editor.focus()
    },
    onCmFocus(cm,a,b) {
      console.log('the editor is focus!',cm,a,b)
      console.log("🚀 ~ file: index.vue:391 ~ insertContent ~ this.editor.getCursor():", this.editor.getCursor())
    },
    modelChange(val) {
      this.curParams.pointer = {};
      delete this.curParams.result.path;
      this.$set(this.data.params, this.stepIndex, this.curParams)
    },
    pointerChange(val) {
      delete this.curParams.result.path;
      this.$set(this.data.params, this.stepIndex, this.curParams)
      this.updateModelPath(val, this.stepIndex)
    },
    menuChange(name) {
      let isStep = false
      if (this.currentMenu !== name) {
        if (/^(step)+/.test(name)) {
          this.selectStep(name.replace('step', ''))
          isStep = true
        } else if (name === 'new') {
          this.newAddStep()
        } else if (name === 'expression') {
          this.calcExp()
          this.selectMenu(name)
        } else {
          this.selectMenu(name)
        }
      }
      !isStep && (this.stepIndex = -1)
    },
    calcExp() {
      let opt = []
      let tmpVarObj = {}

      this.data.params.forEach( (item, index) => {
        let key = item.result.var || 'result' + index
        tmpVarObj[key] = {}
        item.result && item.result.path && item.result.path.length 
          && item.result.path.forEach(pathItem => {
          opt.push(`<#${key}.${pathItem}#>`)
          tmpVarObj[key][pathItem] = index + 1
        })
      })
      this.varObj = tmpVarObj
      this.expParamList = this.tools.getOptions(opt)
      // this.editor.replaceSelection('<#res0.df_count2#>');
      console.log("🚀 ~ file: index.vue:334 ~ calcExp ~ opt:", opt, tmpVarObj)
    },
    updateModelPath(val, index) {
      if (val && val.className && val.type) {
        this.getModel(val).then(res => {
          if (res && res.features && res.features.length){
            this.$set(this.fieldList, index, this.tools.getOptions(res.features, 'fieldName', 'fieldName'))
          } else {
            this.$set(this.fieldList, index, [])
          }
        })
      }
    },
    selectMenu(position) {
      this.currentMenu = position;
    },
    selectStep(pos) {
      this.currentMenu = `step${pos}`;
      this.stepIndex = pos;
    },
    removeStep(pos) {
      this.selectStep(this.stepIndex - 1)
      this.data.params.splice(pos, 1);
    },
    newAddStep() {
      let newItem = this.model.serviceItemTpl()

      newItem.result.var = `res${this.data.params.length}`
      this.data.params.push(newItem);
      this.$nextTick(() => {
        this.selectStep(this.data.params.length - 1);
      })
    },
    submit() {
      let newData = JSON.parse(JSON.stringify(this.data))

      newData.params = this.data.params.filter(item=> !!item.model)
      
      if (!newData.params.length) {
        newData.params = [
          this.model.serviceItemTpl()
        ]
      }
      console.log(JSON.stringify(newData));
      this.$emit('submit', newData)
    },
    removeParam(item) {
      this.$delete(this.data.param, item);
      this.$forceUpdate()
    },
    addParam(item) {
      this.$set(this.data.param, item, '')
      this.$forceUpdate()
    },
  }
}
</script>

<style lang="less">
.c-formulaEditor {
  padding: 2px;
  overflow: hidden;
  .ivu-menu-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 24px;
    line-height: 46px;
  }
  .ivu-col {
    .expWp.hidden {
      position: absolute;
      visibility: hidden;
    }
    .expWp {
      height: 100%;
      display: flex;
      flex-direction: column;
    }
  }
  .btnwp {
    margin-top: 12px;
    .ivu-btn {
      margin-right: 12px;
    }
    .float-right {
      float: right;
    }
    .btnValidate {
      margin: 0;
    }
  }
  .expInput {
    display: block;
    flex: 1;
  }
  .cm-func {
    line-height: 14px;
    margin: 0 1px;
    padding: 0 2px;
    font-weight: 700;
    color: #ae4597;
  }
  .cm-field {
    margin: 0 1px;
    padding: 3px 5px;
    border-radius: 3px;
    color: #fff;
    background: #007bff;
  }
  // .ivu-menu-vertical.ivu-menu-light:after {
    // display: none;
  // }
  // .CodeMirror pre.CodeMirror-line, .CodeMirror pre.CodeMirror-line-like span {
  //   white-space: pre-wrap;
  // }
}
</style>
